package cn.always.xiajia.admin.sys.service.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.Resource;
import javax.validation.Valid;
import javax.validation.Validator;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.xingyuv.captcha.model.common.ResponseModel;
import com.xingyuv.captcha.model.vo.CaptchaVO;
import com.xingyuv.captcha.service.CaptchaService;

import cn.always.xiajia.admin.client.api.oauth2.dto.OAuth2AccessTokenCreateReqDTO;
import cn.always.xiajia.admin.client.api.oauth2.dto.OAuth2AccessTokenRespDTO;
import cn.always.xiajia.admin.client.api.sys.dto.AuthLoginReqVO;
import cn.always.xiajia.admin.client.api.sys.dto.AuthLoginRespVO;
import cn.always.xiajia.admin.client.api.sys.dto.AuthPermissionInfoRespVO;
import cn.always.xiajia.admin.oauth2.service.OAuth2AccessTokenService;
import cn.always.xiajia.admin.sys.entity.SysMenu;
import cn.always.xiajia.admin.sys.entity.SysRole;
import cn.always.xiajia.admin.sys.entity.SysUser;
import cn.always.xiajia.admin.sys.service.SysLoginService;
import cn.always.xiajia.admin.sys.service.SysMenuService;
import cn.always.xiajia.admin.sys.service.SysRoleMenuService;
import cn.always.xiajia.admin.sys.service.SysRoleService;
import cn.always.xiajia.admin.sys.service.SysUserRoleService;
import cn.always.xiajia.admin.sys.service.SysUserService;
import cn.always.xiajia.framework.common.entity.AjaxResult;
import cn.always.xiajia.framework.common.enums.CommonStatusEnum;
import cn.always.xiajia.framework.common.enums.MenuTypeEnum;
import cn.always.xiajia.framework.common.enums.UserTypeEnum;
import cn.always.xiajia.framework.common.enums.XjConstant;
import cn.always.xiajia.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.always.xiajia.framework.common.exception.util.ServiceExceptionUtil;
import cn.always.xiajia.framework.common.util.ExtCollUtil;
import cn.always.xiajia.framework.common.util.ExtCollectionUtils;
import cn.always.xiajia.framework.common.util.validation.ValidationUtils;
import cn.always.xiajia.framework.security.core.util.SecurityFrameworkUtils;
import cn.hutool.core.collection.CollUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * 登录中心
 * 
 * @author xgj
 *
 */
@Slf4j
@Service("sysLoginService")
public class SysLoginServiceImpl implements SysLoginService {

	@Resource
	protected SysUserService sysUserService;

	@Resource
	protected OAuth2AccessTokenService oAuth2AccessTokenService;

	@Resource
	protected Validator validator;

	@Resource
	protected CaptchaService captchaService;

	@Resource
	protected SysUserRoleService sysUserRoleService;

	@Resource
	protected SysRoleService sysRoleService;

	@Resource
	protected SysRoleMenuService sysRoleMenuService;

	@Resource
	protected SysMenuService sysMenuService;

	/**
	 * 验证码的开关，默认为 true
	 */
	@Value("${xj.captcha.enable:true}")
	protected Boolean captchaEnable;

	/**
	 * 验证账号 + 密码。如果通过，则返回用户
	 *
	 * @param username 账号
	 * @param password 密码
	 * @return 用户
	 */
	@Override
	public SysUser authenticate(String userCode, String password) {
		Map<String, Object> param = new HashMap<>();
		param.put("EQ_userCode", userCode);
		SysUser sysUser = sysUserService.xjGetOne(param);
		if (sysUser == null) {
			// 记录日志暂时不开发

			throw ServiceExceptionUtil.exception(GlobalErrorCodeConstants.ADMIN_000004);
		}
		if (!sysUserService.isPasswordMatch(password, sysUser.getPassword())) {
			// 记录日志暂时不开发

			throw ServiceExceptionUtil.exception(GlobalErrorCodeConstants.ADMIN_000004);
		}
		// 校验是否禁用
		if (CommonStatusEnum.isDisable(sysUser.getStatus())) {
			// 记录日志暂时不开发

			throw ServiceExceptionUtil.exception(GlobalErrorCodeConstants.ADMIN_000005);
		}
		return sysUser;
	}

	/**
	 * 账号登录
	 *
	 * @param reqVO 登录信息
	 * @return 登录结果
	 */
	@Override
	public AuthLoginRespVO login(@Valid AuthLoginReqVO reqVO) {
		// 校验验证码
		validateCaptcha(reqVO);

		// 后续考虑加密传参rsa
		SysUser sysUser = authenticate(reqVO.getUserCode(), reqVO.getPassword());

		// TODO Auto-generated method stub
		return createTokenAfterLoginSuccess(sysUser);
	}

	protected AuthLoginRespVO createTokenAfterLoginSuccess(SysUser sysUser) {
		// 插入登陆日志

		// 创建访问令牌
		OAuth2AccessTokenCreateReqDTO oAuth2AccessTokenCreateReqDTO = new OAuth2AccessTokenCreateReqDTO();
		oAuth2AccessTokenCreateReqDTO.setUserId(sysUser.getUserId());
		oAuth2AccessTokenCreateReqDTO.setUserCode(sysUser.getUserCode());
		oAuth2AccessTokenCreateReqDTO.setUserName(sysUser.getUserName());
		oAuth2AccessTokenCreateReqDTO.setUserType(sysUser.getUserType());
		oAuth2AccessTokenCreateReqDTO.setClientId(XjConstant.CLIENT_ID_DEFAULT);

		OAuth2AccessTokenRespDTO accessTokenDO = oAuth2AccessTokenService.createAccessToken(oAuth2AccessTokenCreateReqDTO);

		// 构建返回结果
		AuthLoginRespVO authLoginRespVO = new AuthLoginRespVO().setUserId(accessTokenDO.getUserId())
				.setUserCode(accessTokenDO.getUserCode()).setUserName(accessTokenDO.getUserName()).setUserType(accessTokenDO.getUserType())
				.setAccessToken(accessTokenDO.getAccessToken()).setRefreshToken(accessTokenDO.getRefreshToken())
				.setExpiresTime(accessTokenDO.getExpiresTime());
		return authLoginRespVO;
	}

	/**
	 * 校验验证码
	 * 
	 * @param reqVO
	 */
	protected void validateCaptcha(AuthLoginReqVO reqVO) {
		// 如果验证码关闭，则不进行校验
		if (!captchaEnable) {
			return;
		}

		// 校验验证码
		ValidationUtils.validate(validator, reqVO, AuthLoginReqVO.CodeEnableGroup.class);

		CaptchaVO captchaVO = new CaptchaVO();
		captchaVO.setCaptchaVerification(reqVO.getCaptchaVerification());
		ResponseModel response = captchaService.verification(captchaVO);
		// 验证不通过
		if (!response.isSuccess()) {
			// 记录日志暂时不开发（验证码不正确)

			throw ServiceExceptionUtil.exception(GlobalErrorCodeConstants.ADMIN_000006, response.getRepMsg());
		}
	}

	/**
	 * 基于 token 退出登录
	 *
	 * @param token token
	 * @param logType 登出类型
	 */
	@Override
	public void logout(String token, Integer logType) {
		// 删除访问令牌
		OAuth2AccessTokenRespDTO accessTokenDO = oAuth2AccessTokenService.removeAccessToken(token);
		if (accessTokenDO == null) {
			return;
		}
		// 删除成功，记录日志暂时不开发

	}

	/**
	 * 刷新访问令牌
	 *
	 * @param refreshToken 刷新令牌
	 * @return 登录结果
	 */
	@Override
	public AuthLoginRespVO refreshToken(String refreshToken) {
		OAuth2AccessTokenRespDTO accessTokenDO = oAuth2AccessTokenService.refreshAccessToken(refreshToken, XjConstant.CLIENT_ID_DEFAULT);
		// 构建返回结果
		AuthLoginRespVO authLoginRespVO = new AuthLoginRespVO().setUserId(accessTokenDO.getUserId())
				.setUserCode(accessTokenDO.getUserCode()).setUserName(accessTokenDO.getUserName()).setUserType(accessTokenDO.getUserType())
				.setAccessToken(accessTokenDO.getAccessToken()).setRefreshToken(accessTokenDO.getRefreshToken())
				.setExpiresTime(accessTokenDO.getExpiresTime());
		return authLoginRespVO;
	}

	/**
	 * 获取登录用户的权限信息
	 * 
	 * @return
	 */
	@Override
	public AjaxResult getPermissionInfo() {
		// 1.1 获得用户信息
		SysUser user = sysUserService.getUser(SecurityFrameworkUtils.getLoginUserId());
		if (user == null) {
			return null;
		}

		AuthPermissionInfoRespVO authPermissionInfoRespVO = new AuthPermissionInfoRespVO();
		authPermissionInfoRespVO.setUser(AuthPermissionInfoRespVO.UserVO.builder().userId(user.getUserId()).userCode(user.getUserCode())
				.userName(user.getUserName()).userType(user.getUserType()).email(user.getEmail()).mobile(user.getMobile())
				.sex(user.getSex()).avatar(user.getAvatar()).build());
		// 是否超级管理
		Boolean isSuperAdmin = UserTypeEnum.SUPERADMIN.getValue().toString().equals(user.getUserType());
		// 获得角色列表
		Set<Long> roleIds = sysUserRoleService.getRoleIdByUserId(SecurityFrameworkUtils.getLoginUserId());
		if (CollUtil.isEmpty(roleIds)) {
			return AjaxResult.success(authPermissionInfoRespVO);
		}

		List<SysRole> roles = sysRoleService.getSysRoleByRoleIds(roleIds);
		roles.removeIf(role -> !CommonStatusEnum.ENABLE.getStatus().toString().equals(role.getStatus())); // 移除禁用的角色
		if (!ExtCollUtil.isEmpty(roles)) {
			authPermissionInfoRespVO.setRoles(ExtCollectionUtils.convertSet(roles, SysRole::getRoleCode));
		}

		// 获得菜单列表
		List<SysMenu> menuList = new ArrayList<>();
		if (isSuperAdmin) {
			menuList = sysMenuService.list();
		} else {
			Set<Long> menuIds = sysRoleMenuService.getMenuIdByRoleIds(ExtCollectionUtils.convertSet(roles, SysRole::getRoleId));
			menuList = sysMenuService.getMenuListByMenuIds(menuIds);
		}
		menuList.removeIf(menu -> !CommonStatusEnum.ENABLE.getStatus().toString().equals(menu.getStatus())); // 移除禁用的菜单
		if (!ExtCollUtil.isEmpty(menuList)) {
			authPermissionInfoRespVO.setPermissions(ExtCollectionUtils.convertSet(menuList, SysMenu::getPerms));
		}

		// 菜单树
		authPermissionInfoRespVO.setMenus(buildMenuTree(menuList));

		return AjaxResult.success(authPermissionInfoRespVO);
	}

	/**
	 * 构建菜单树
	 * 
	 * @param menuList
	 * @return
	 */
	protected List<AuthPermissionInfoRespVO.MenuVO> buildMenuTree(List<SysMenu> menuList) {
		if (CollUtil.isEmpty(menuList)) {
			return Collections.emptyList();
		}
		// 移除按钮
		menuList.removeIf(menu -> menu.getMenuType().equals(MenuTypeEnum.F.getTerminal()));
		// 排序，保证菜单的有序性
		menuList.sort(Comparator.comparing(SysMenu::getOrderNum));

		// 使用 LinkedHashMap 的原因，是为了排序 。实际也可以用 Stream API ，就是太丑了。
		Map<Long, AuthPermissionInfoRespVO.MenuVO> treeNodeMap = new LinkedHashMap<>();
		menuList.forEach(menu -> treeNodeMap.put(menu.getMenuId(), convertTreeNode(menu)));
		// 处理父子关系
		treeNodeMap.values().stream().filter(node -> !node.getParentId().equals(SysMenu.ID_ROOT)).forEach(childNode -> {
			// 获得父节点
			AuthPermissionInfoRespVO.MenuVO parentNode = treeNodeMap.get(childNode.getParentId());
			if (parentNode == null) {
				log.info(childNode.getMenuId() + "找不到父资源ParentId:" + childNode.getParentId());
				return;
			}
			// 将自己添加到父节点中
			if (parentNode.getChildren() == null) {
				parentNode.setChildren(new ArrayList<>());
			}
			parentNode.getChildren().add(childNode);
		});
		// 获得到所有的根节点
		return ExtCollectionUtils.filterList(treeNodeMap.values(), node -> SysMenu.ID_ROOT.equals(node.getParentId()));
	}

	/**
	 * 菜单赋值
	 * 
	 * @param menu
	 * @return
	 */
	protected AuthPermissionInfoRespVO.MenuVO convertTreeNode(SysMenu sysMenu) {
		if (sysMenu == null) {
			return null;
		}
		AuthPermissionInfoRespVO.MenuVO.MenuVOBuilder menuVO = AuthPermissionInfoRespVO.MenuVO.builder();
		menuVO.menuId(sysMenu.getMenuId());
		menuVO.parentId(sysMenu.getParentId());
		menuVO.name(sysMenu.getMenuName());
		menuVO.nameZhCn(sysMenu.getMenuZhCn());
		menuVO.path(sysMenu.getPath());
		menuVO.component(sysMenu.getComponent());
		menuVO.icon(sysMenu.getIcon());
		menuVO.visible("0".equals(sysMenu.getVisible()) ? true : false);
		menuVO.isCache("0".equals(sysMenu.getIsCache()) ? true : false);

		return menuVO.build();
	}
}
